#!/usr/bin/env python3
"""What the call agent reads, in one command, so a start or a new call is one tool call instead of a dozen
model turns.

  aux-boot                              # at the start of the session: the wiki, and the command to park on
  aux-boot --env <env> --call <id>      # when a call starts: who is on it, their pages, what changed in the wiki

The agent is one long-lived session (aux-agent starts it before any call). With no arguments this prints the
wiki context (me.md, home.md, the latest call pages, what landed on them) and, under NEXT, the `aux-listen`
command it parks on. aux-listen prints an `[aux] call …` line when a call starts, naming the second form, which
prints the participants and their profiles plus only the wiki pages changed since this session last read them
(the previous call's page among them, once its update has landed), and tells Aux the agent is listening.
"""
import argparse
import subprocess
import sys
import time
from pathlib import Path

# Inside an app bundle (Tuple.app/Contents/Helpers/Tuple Aux.app) a __pycache__ write is macOS's
# "python3 was prevented from modifying apps" prompt; the import never needs one.
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path(__file__).resolve().parent))
from aux_context import (AUX, CLIS, WIKI, agent_record, cli_path, participants, profile, read, status,  # noqa: E402
                              title_notebook, update_agent)

RECENT_CALLS = 6
CALL_HEAD_LINES = 14
LANDED_CALLS = 10       # call pages whose Agent contributions ledger is shown at attach
LANDED_CHARS = 12_000   # cap on that section; the oldest pages drop first


def section(title, body):
    print(f"\n===== {title} =====\n{body.rstrip()}")


def mtime(path):
    try:
        return path.stat().st_mtime
    except OSError:
        return 0


def contributions(page):
    """A call page's `## Agent contributions` section, or "" when it has none or sent nothing."""
    out, inside = [], False
    for line in (read(page) or "").splitlines():
        if line.startswith("## "):
            inside = line.strip() == "## Agent contributions"
        if inside:
            out.append(line)
    text = "\n".join(out[1:]).strip()
    return "" if not text or text.startswith("**0 sent") else text


def print_landed(since=0.0):
    """What landed: the ledgers of the last calls that sent cards, newest first. Computed here, never stored
    (the wiki's schema forbids a summary page a grep could rebuild). The agent reads these as what this
    operator acts on, in place of any rule of thumb."""
    pages = []
    for c in reversed(sorted((WIKI / "calls").glob("**/*.md"))):
        text = contributions(c)
        if text:
            pages.append((c, text))
        if len(pages) == LANDED_CALLS:
            break
    if since and not any(mtime(c) > since for c, _ in pages):
        return
    # Whole ledgers while they fit, then just the scoreline, so the trend still covers every call.
    parts, size = [], 0
    for c, text in pages:
        part = f"--- {c.relative_to(WIKI)}\n{text}"
        if size + len(part) > LANDED_CHARS and parts:
            part = f"--- {c.relative_to(WIKI)} (scoreline only)\n{text.splitlines()[0]}"
        parts.append(part)
        size += len(part)
    section(f"wiki/calls (what landed, last {len(parts)} calls with cards)", "\n\n".join(parts) or "(none yet)")


def print_wiki(people=None, since=0.0):
    """me.md, home.md, each participant's profile and the heads of the latest call pages. With `since`,
    only the pages changed after it (profiles always), so a call's start prints what the session hasn't read."""
    people = people or {}
    for name in ("me.md", "home.md"):
        if mtime(WIKI / name) > since:
            section(f"wiki/{name}", read(WIKI / name) or "(missing)")
    for uid, name in people.items():
        p = profile(uid, name)
        if p and p.name != "me.md":
            section(f"wiki/{p.relative_to(WIKI)}", read(p, limit=6000))
    calls = sorted((WIKI / "calls").glob("**/*.md"))[-RECENT_CALLS:]
    calls = [c for c in calls if mtime(c) > since]
    heads = []
    for c in reversed(calls):
        head = "\n".join(read(c).splitlines()[:CALL_HEAD_LINES])
        heads.append(f"--- {c.relative_to(WIKI)}\n{head}")
    if heads or not since:
        label = "changed since you last read them" if since else f"latest {len(calls)}"
        section(f"wiki/calls ({label}, first {CALL_HEAD_LINES} lines each)", "\n\n".join(heads) or "(none)")
    print_landed(since)


def start():
    """The session's start: the wiki now, then park on aux-listen."""
    rec = agent_record()
    section("CALL", "none yet. You are Aux's call agent for every call from now on. Read the wiki below, then park on the "
            "command under NEXT and do nothing else until it prints a line. Write nothing to the console.")
    print_wiki()
    update_agent({"wikiLoadedAt": time.time()}, only_id=rec.get("id"))
    listen = Path(__file__).resolve().parent / "aux-listen"
    section("NEXT", "Start the Monitor (timeout_ms 1800000) with exactly this command; each line it prints is an event:\n"
            f"AUX_HOME='{AUX}' '{listen}' --id {rec.get('id', '<your short session id from claude agents>')}")
    return 0


def call(env, call_id):
    """A call has started (aux-listen said so): who is on it, their pages, and what changed in the wiki since
    this session last read it."""
    cli = cli_path(env) or CLIS[env]
    rec = agent_record()
    since = float(rec.get("wikiLoadedAt") or 0)
    people = participants(cli, call_id)
    title_notebook(call_id, people)
    rows = []
    for uid, name in people.items():
        p = profile(uid, name)
        rows.append(f"- {name} (user_id {uid}): " + (str(p.relative_to(WIKI)) if p else "no page yet"))
    section("CALL", f"env: {env}\nCLI: {cli}\ncall id: {call_id}\n"
            f"Every note for this call: {Path(__file__).resolve().parent / 'aux'} --call {call_id} …\n"
            f"participants now:\n" + ("\n".join(rows) or "- none yet besides the operator") +
            ("\n\nme.md, home.md and the earlier call pages are already in your context; only pages changed since "
             "you read them follow." if since else ""))
    print_wiki(people, since)
    update_agent({"wikiLoadedAt": time.time()}, only_id=rec.get("id"))
    status("listening", call=call_id)
    return 0


def avatar_pass():
    """Teammates' faces, in the background, at most once a day. Pages with no `Avatar:` line get one, and
    `none` ones are tried again (--force): a new machine's pages were checked before Tuple's own avatar_url
    reached the CLI, and Linear (where most faces came from) is only on some operators' machines. The
    agent's turn never waits on it; fetch-avatar.sh prints only names and sources to the log."""
    marker = AUX / "avatar-pass"
    today = time.strftime("%Y-%m-%d")
    try:
        if marker.read_text().strip() == today:
            return
    except OSError:
        pass
    here = Path(__file__).resolve().parent
    script = next((p for p in (here / "fetch-avatar.sh", here.parent / "plugin" / "wiki-templates" / "fetch-avatar.sh")
                   if p.exists()), None)
    if script is None:
        return
    # Older wikis' AGENTS.md call the copy init-wiki.sh made once; keep it the current one.
    copy = WIKI / ".bin" / "fetch-avatar.sh"
    try:
        if copy.parent.exists() and (not copy.exists() or copy.read_bytes() != script.read_bytes()):
            copy.write_bytes(script.read_bytes()); copy.chmod(0o755)
    except OSError:
        pass
    pages = []
    for page in sorted((WIKI / "people").glob("*/*.md")):
        head = "\n".join((read(page) or "").splitlines()[:20])
        line = next((l for l in head.splitlines() if l.startswith("- Avatar:")), None)
        if line is None:
            pages.append([str(page)])
        elif line.startswith("- Avatar: none"):
            pages.append([str(page), "--force"])
    if not pages:
        return
    marker.write_text(today + "\n")
    cmds = " ; ".join("'" + str(script) + "' " + " ".join("'" + x.replace("'", "") + "'" for x in p) for p in pages)
    log = open(AUX / "avatar-pass.log", "a")
    log.write(f"\n--- {time.strftime('%Y-%m-%d %H:%M:%S')} {len(pages)} pages\n")
    log.flush()
    subprocess.Popen(["/bin/bash", "-c", cmds], stdout=log, stderr=log, stdin=subprocess.DEVNULL, start_new_session=True)


def main():
    # First line of every boot: the data folder, for a shell that doesn't have $AUX_HOME (a `claude --bg` session's).
    print(f"AUX_HOME={AUX}")
    ap = argparse.ArgumentParser()
    ap.add_argument("--env", choices=CLIS)
    ap.add_argument("--call")
    a = ap.parse_args()
    avatar_pass()
    if a.call:
        return call(a.env or agent_record().get("env") or "prod", a.call)
    return start()


if __name__ == "__main__":
    sys.exit(main())
